/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.dusa.worklog.controller;

import cz.dusa.worklog.datalib.Customer;
import cz.dusa.worklog.datalib.Project;
import cz.dusa.worklog.datalib.WorklogData;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.swing.JOptionPane;

/**
 * Singelton class encapsulating communication with the database. The database is a embedded derby db
 * created automatically in the user.dir/database directory.
 * @author vdusa
 */
public class WorklogDataManager {

    private static WorklogDataManager instance = null;
    private EntityManager em = null;
    private Map<Class, Map<Object, List<WorklogDataListener>>> worklogDataListeners = new HashMap<Class, Map<Object, List<WorklogDataListener>>>();
    private final Object NULL_ID = null;

    private WorklogDataManager() {
        System.out.println("Database uses folder: " + System.getProperty("user.dir") + File.separator + "database");
        System.setProperty("derby.system.home", System.getProperty("user.dir") + File.separator + "database");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("work-log-datalibPU");
        em = emf.createEntityManager();
    }

    /**
     * returns singelton instance of WorklogDataManager
     */
    public static WorklogDataManager getInstance() {
        if (instance == null) {
            instance = new WorklogDataManager();
        }
        return instance;
    }

    /**
     * Persists the object in the database. persist is used for new objects, that
     * doesn't exist in the database yet.
     * @param object
     * @throws WorklogDataException
     */
    public WorklogData persist(WorklogData object) throws WorklogDataException {
        em.getTransaction().begin();
        try {
            em.persist(object);
            em.getTransaction().commit();

            //we let know listeners of the current id
            List<WorklogDataListener> listeners = getWorklogDataListeners(object.getClass(), object.getId());            
            if(listeners != null){
                ArrayList<WorklogDataListener> idListeners = new ArrayList<WorklogDataListener>(listeners);
                for (WorklogDataListener wdl : idListeners) {
                    wdl.afterCreate(object);
                }
            }

            //we let know listeners of the whole class
            listeners = getWorklogDataListeners(object.getClass());
            if(listeners != null){
                ArrayList<WorklogDataListener> commonListeners = new ArrayList<WorklogDataListener>(listeners);
                for (WorklogDataListener wdl : commonListeners) {
                    wdl.afterCreate(object);
                }
            }

            return object;
        } catch (Exception e) {
            em.getTransaction().rollback();
            throw new WorklogDataException(e.getMessage());
        }
    }

    /**
     * removes the object from the database
     * @param object
     * @throws WorklogDataException
     */
    public void remove(WorklogData object) throws WorklogDataException {
        em.getTransaction().begin();
        
        //we let know listeners of the current id
        List<WorklogDataListener> listeners = getWorklogDataListeners(object.getClass(), object.getId());
        if(listeners != null){
            for (WorklogDataListener wdl : listeners) {
                wdl.beforeDelete(object);
            }
        }

        //we let know listeners of the whole class
        listeners = getWorklogDataListeners(object.getClass());
        if(listeners != null){
            for (WorklogDataListener wdl : listeners) {
                wdl.beforeDelete(object);
            }
        }
        
        try {
            em.remove(object);
            em.getTransaction().commit();
        } catch (Exception e) {
            if(em.getTransaction().isActive()){
                em.getTransaction().rollback();
            }
            throw new WorklogDataException(e.getMessage());
        }
    }

    /**
     * Saves sthe object in the database. merge is used for objects, that already
     * exists in the database.
     * @param object
     * @return refreshed object. From now you don't have to use the object given this function
     * as a parameter, but you must use this returned object.
     * @throws WorklogDataException
     */
    public Object merge(WorklogData object) throws WorklogDataException {
        WorklogData result;

        try {
            em.getTransaction().begin();
            result = (WorklogData) em.merge(object);
            em.getTransaction().commit();
        } catch (Exception e) {
            em.getTransaction().rollback();
            throw new WorklogDataException(e.getMessage());
        }

        //we let know listeners of the current id
        List<WorklogDataListener> listeners = getWorklogDataListeners(object.getClass(), object.getId());
        if(listeners != null){
            for (WorklogDataListener wdl : listeners) {
                wdl.afterUpdate(result);
            }
        }

        //we let know listeners of the whole class
        listeners = getWorklogDataListeners(object.getClass());
        if(listeners != null){
            for (WorklogDataListener wdl : listeners) {
                wdl.afterUpdate(result);
            }
        }

        return result;
    }

    /**
     * @param entityClass
     * @param primaryKey
     * @return an instance of the entityClass object existing in the database according to the
     * given primaryKey.
     */
    public Object find(Class entityClass, Object primaryKey) {
        return em.find(entityClass, primaryKey);
    }

    /**
     * @return collection of all customers stored in the database.
     */
    public List<Customer> getAllCustomers() {
        return em.createNamedQuery("getAllCustomers").getResultList();
    }

    /**
     * @return collection of all projects stored in the database
     */
    public List<Project> getAllProjects() {
        return em.createNamedQuery("getAllProjects").getResultList();
    }

    /**
     * Adds a new WorklogDataListener. The listener wdl is invoked as soon as the object of the class listenToClass with given id is inserted/changed/removed in the database.
     * @param wdl
     * @param listenToClass
     * @param id
     * @see WorklogDataListener
     */
    public void addWorklogDataListener(WorklogDataListener wdl, Class listenToClass, Object id) {
        Map<Object, List<WorklogDataListener>> listenersMap = worklogDataListeners.get(listenToClass);
        List<WorklogDataListener> listeners;

        if (listenersMap==null){
            //there is no listener for the whole listenToClass => we create one with listeners
            listeners = new ArrayList<WorklogDataListener>();            
            listenersMap = new HashMap<Object, List<WorklogDataListener>>();
            listenersMap.put(id, listeners);
            worklogDataListeners.put(listenToClass, listenersMap);
        }
        else{
            listeners = listenersMap.get(id);
            if (listeners == null){
                //there is no listener for this id => we create one
                listeners = new ArrayList<WorklogDataListener>();                
                listenersMap.put(id, listeners);
            }
        }

        listeners.add(wdl);
    }

    /**
     * Removes given WorklogDataListener listening on the class listenToClass and given id
     * @param wdl
     * @param listenToClass
     * @param id
     * @see WorklogDataListener
     */
    public void removeWorklogDataListener(WorklogDataListener wdl, Class listenToClass, Object id) {
        Map<Object, List<WorklogDataListener>> listenersMap = worklogDataListeners.get(listenToClass);
        List<WorklogDataListener> listeners;

        if (listenersMap==null){
            //there is no listener for the whole listenToClass => we create one with listeners
            return;
        }
        else{
            listeners = listenersMap.get(id);
            if (listeners == null){
                //there is no listener for this id => we create one
                return;
            }
        }

        //remove the listener
        listeners.remove(wdl);

        //if there is no other listener for this id, we remove the list too
        if(listeners.isEmpty()){
            listenersMap.remove(id);
            //if there is no other listener for this class, we remove the map too
            if(listenersMap.isEmpty()){
                worklogDataListeners.remove(listenToClass);
            }
        }
    }

    /**
     * Adds a new WorklogDataListener. The listener wdl is invoked as soon as the object of the class listenToClass is inserted/changed/removed in the database (independent from id).
     * @param wdl
     * @param listenToClass
     * @see WorklogDataListener
     */
    public void addWorklogDataListener(WorklogDataListener wdl, Class listenToClass){
        addWorklogDataListener(wdl, listenToClass, NULL_ID);
    }

    /**
     * Removes given WorklogDataListener listening on the class listenToClass (added by the function addWorklogDataListener(WorklogDataListener, Class))
     * @param wdl
     * @param listenToClass
     * @param id
     * @see WorklogDataListener
     * @see addWorklogDataListener(WorklogDataListener, Class)
     */
    public void removeWorklogDataListener(WorklogDataListener wdl, Class listenToClass) {
        removeWorklogDataListener(wdl, listenToClass, NULL_ID);
    }

    private List<WorklogDataListener> getWorklogDataListeners(Class listenToClass, Object id){
        Map<Object, List<WorklogDataListener>> listenersMap = worklogDataListeners.get(listenToClass);
        List<WorklogDataListener> idListeners;

        if (listenersMap==null){
            //there is no listener for the whole listenToClass => we create one with listeners
            return null;
        }
        else{
            idListeners = listenersMap.get(id);
            if (idListeners == null){
                //there is no listener for this id => we create one
                return null;
            }
        }

        return idListeners;
    }

    private List<WorklogDataListener> getWorklogDataListeners(Class listenToClass){
        Map<Object, List<WorklogDataListener>> listenersMap = worklogDataListeners.get(listenToClass);
        List<WorklogDataListener> listeners;

        if (listenersMap==null){
            //there is no listener for the whole listenToClass => we create one with listeners
            return null;
        }
        else{
            listeners = listenersMap.get(NULL_ID);
            if (listeners == null){
                //there is no listener for this id => we create one
                return null;
            }
        }

        return listeners;
    }

    public Customer getNewCustomer(){
        return new Customer();
    }

    public Project getNewProject(Customer customer){
        Project project = new Project();
        project.setCustomer(customer);
        return project;
    }
}
